#ifndef __COMPUTE_PREDS_HELPER_HPP__
#define __COMPUTE_PREDS_HELPER_HPP__

#include <cassert>
#include <tuple>

// Depth N-1 does   1 updates to std::get<N>
// Depth N-2 does   2 updates to std::get<N-1>
// ...
// Depth N-k does N-k updates to std::get<N-k>
// ...
// Depth   1 does   N updates to std::get<1>
// Depth   0 does   0 updates to std::get<1>
// -> must start with N = INTERTASKDIM-1 and K=1
template<int TASKTAGDIM, int INTERTASKDIM, int N, int K> struct computePredsHelper2 {
  template<typename...CONDSE, typename...DEPDISTS, typename...LBSE, 
           typename...UBSE, typename... INTS>
  static void compute(std::tuple<CONDSE...> &condsE, std::tuple<DEPDISTS...> const & dists, 
                      std::tuple<LBSE...> const & lbsE, std::tuple<UBSE...> const & ubsE, 
                      std::tuple<INTS...> & tagAsTupleE)
  {
#if (DEBUG_ET_DEPS && VERBOSE >= 100)
    std::cerr << "At loop " << N << " cond is " << std::get<N>(condsE) << "\n";
#endif

    if (std::get<N>(dists) != 0) {
      auto tmp = std::get<N>(tagAsTupleE);
      std::get<N>(tagAsTupleE) = tmp - std::get<N>(dists);  // DEP2
      std::get<N>(condsE) |= 
        !(std::get<INTERTASKDIM-K+1>(lbsE).Val(tagAsTupleE)) ||
        !(std::get<INTERTASKDIM-K+1>(ubsE).Val(tagAsTupleE));
      assert(std::get<N>(tagAsTupleE) == tmp - std::get<N>(dists));  // DEP2
      std::get<N>(tagAsTupleE) = tmp;
      assert(std::get<N>(tagAsTupleE) == tmp);
    }

#if (DEBUG_ET_DEPS && VERBOSE >= 100)
    std::cerr << "\t\tEvaluating expression for loop " << N << " at loop iteration: ";
    std::cerr << std::get<1>(tagAsTupleE) << " ";
    std::cerr << std::get<2>(tagAsTupleE) << " ";
    std::cerr << std::get<3>(tagAsTupleE) << " ";
    std::cerr << std::get<4>(tagAsTupleE) << " ";
    std::cerr << std::get<5>(tagAsTupleE) << " ";
    std::cerr << " gives " << std::get<N>(condsE) << "\n";
#endif

    computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, K-1>::compute
      (condsE, dists, lbsE, ubsE, tagAsTupleE);
  }
};


template<int TASKTAGDIM, int INTERTASKDIM, int N> 
struct computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, 0> {
  template<typename...CONDSE, typename...DEPDISTS, typename...LBSE, 
           typename...UBSE, typename... INTS>
  static void compute(std::tuple<CONDSE...> &, std::tuple<DEPDISTS...> const  &, 
                      std::tuple<LBSE...> const &, std::tuple<UBSE...> const &,
                      std::tuple<INTS...> &) {}
};


// Depth N-1 does   1 updates to std::get<N>
// Depth N-2 does   2 updates to std::get<N-1>
// ...
// Depth N-k does N-k updates to std::get<N-k>
// ...
// Depth   1 does   N updates to std::get<1>
// Depth   0 does   0 updates to std::get<1>
// -> must start with N = INTERTASKDIM-1 and K=1
template<int TASKTAGDIM, int INTERTASKDIM, int K, int N> struct computePredsHelper {
  template<typename...CONDSE, typename...DEPDISTS, typename...LBSE, 
           typename...UBSE, typename... INTS>
  static void compute(std::tuple<CONDSE...> &condsE, std::tuple<DEPDISTS...> const &dists,
                      std::tuple<LBSE...> const & lbsE, std::tuple<UBSE...> const &ubsE, 
                      std::tuple<INTS...> & tagAsTupleE)
  {
    computePredsHelper2<TASKTAGDIM, INTERTASKDIM, N, K>::compute
      (condsE, dists, lbsE, ubsE, tagAsTupleE);

    computePredsHelper<TASKTAGDIM, INTERTASKDIM, K+1, N-1>::compute
      (condsE, dists, lbsE, ubsE, tagAsTupleE);
  }
};


template<int TASKTAGDIM, int INTERTASKDIM, int K> 
struct computePredsHelper<TASKTAGDIM, INTERTASKDIM, K, 0> {
  template<typename...CONDSE, typename...DEPDISTS, typename...LBSE, 
           typename...UBSE, typename... INTS>
  static void compute(std::tuple<CONDSE...> &, std::tuple<DEPDISTS...> const &, 
                      std::tuple<LBSE...> const &, std::tuple<UBSE...> const &, 
                      std::tuple<INTS...> &) {}
};

#endif // __COMPUTE_PREDS_HELPER_HPP__
